/* Copyright */
/*******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Tsunagi-BPM
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *******************************************************************************/
 /* Copyright End */
 
package tsunagi.json.path;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import tsunagi.json.path.condition.ConditionHandler;

public class JsonPath {
	private JSONObject json;
	
	public JsonPath(String jsonString) {
		this.setJson(jsonString);
	}

	public JSONObject getJson() {
		return json;
	}

	public void setJson(String jsonString) {
		this.json = new JSONObject(jsonString);
	}
	
	/**
	 * Return the path's result.
	 * 
	 * @param path
	 * @return Returned object may be a String object, a JSONObject, or a List object.
	 * When the List object is returned, the inside may be String or JSONObject objects.
	 */
	public Object get(String path) {
		Tokenizer tokenizer = new Tokenizer(path);
		Object result = this.getJson();
		String token = null;
		StringBuffer tokenTrace = new StringBuffer();
		while((token = tokenizer.next()) != null) {
			ConditionHandler condHandler = null;
			tokenTrace.append(Tokenizer.DELIM + token);
			if (tokenizer.arrayConditionExists(token)) {
				condHandler = new ConditionHandler();
				condHandler.startEvaluation(token);
				token = JsonPathUtil.INSTANCE.removeCondition(token);
			}
				
			JsonObjectType type = null;
			if (result instanceof JSONObject) {
				type = JsonPathUtil.INSTANCE.identifyObjectType((JSONObject)result, token);
			} else { // result is List; select first one to check
				int size = ((List)result).size();
				for(int i=0; i<size; i++) {
					type = JsonPathUtil.INSTANCE.identifyObjectType((JSONObject)((List)result).get(i), token);
					if (type != null) {
						break;
					}
				}
			}
			if (JsonObjectType.ARRAY.equals(type)) {
				result = processArray(token, result, condHandler);
			} else if(JsonObjectType.OBJECT.equals(type)) {
				result = processObject(token, result, condHandler);
			} else if (JsonObjectType.PROPERTY.equals(type)) {
				result = processProperty(token, result);
			} else {
				throw new JsonPathException("Path = [" + tokenTrace + "] not found.");
			}
			
		}
		return result;
	}
	
	protected Object processArray(String token, Object result, ConditionHandler condHandler) {
		List<JSONObject> list = null;
		if (result instanceof List) {
			list = new ArrayList<JSONObject>(((List<JSONObject>)result).size());
			for(JSONObject obj: (List<JSONObject>)result) {
				try {
					JSONArray array = obj.getJSONArray(token);
					int size = array.length();
					for(int i=0; i<size; i++) {
						JSONObject jObj = array.getJSONObject(i);
						if (condHandler == null) {
							list.add(jObj);
						} else {
							condHandler.initConditionCheck();
							if (JsonPathUtil.INSTANCE.isConditionMet(jObj, condHandler)) {
								list.add(jObj);
							}
						}
					}
				} catch (JSONException e) {
					// do nothing
				}
			}
		} else { // must be JSONObject
			JSONArray array = ((JSONObject)result).getJSONArray(token);
			int size = array.length();
			list = new ArrayList<JSONObject>(size);
			for(int i=0; i<size; i++) {
				JSONObject jObj = array.getJSONObject(i);
				if (condHandler == null) {
					list.add(jObj);
				} else {
					condHandler.initConditionCheck();
					if (JsonPathUtil.INSTANCE.isConditionMet(jObj, condHandler)) {
						list.add(jObj);
					}
				}
			}
		}
		return list;
	}

	protected Object processObject(String token, Object result, ConditionHandler condHandler) {
		if (result instanceof List) {
			List<JSONObject> list = new ArrayList<JSONObject>(((List<JSONObject>)result).size());
			for(JSONObject obj: (List<JSONObject>)result) {
				try {
					JSONObject jObj = obj.getJSONObject(token);
					if (condHandler == null) {
						list.add(jObj);
					} else {
						condHandler.initConditionCheck();
						if (JsonPathUtil.INSTANCE.isConditionMet(jObj, condHandler)) {
							list.add(jObj);
						}
					}
				} catch (JSONException e) {
					// do nothing
				}
			}
			result = list;
		} else { // must be JSONObject
			try {
				JSONObject obj = ((JSONObject)result).getJSONObject(token);
				if (condHandler == null) {
					result = obj;
				}else {
					condHandler.initConditionCheck();
					if (JsonPathUtil.INSTANCE.isConditionMet(obj, condHandler)) { 
						result = obj;
					} else {
						result = null;
					}
				}
			} catch (JSONException e) {
				// do nothing
			}
		}
		return result;
	}

	protected Object processProperty(String token, Object result) {
		Object val = null;
		if (result instanceof List) {
			List<String> list = new ArrayList<String>(((List<JSONObject>)result).size());
			for(JSONObject obj: (List<JSONObject>)result) {
				try {
					val = obj.get(token);
					list.add(val.toString());
				} catch (JSONException e) {
					// do nothing
				}
			}
			result = list;
		} else { // must be JSONObject
			try {
				val = ((JSONObject)result).get(token);
				result = val.toString();
			} catch (JSONException e) {
				// do nothing
			}
		}
		return result;
	}
}
